"""
oyAuxiliaryFunction by Erkan Ozgur Yilmaz (c) 2009-2010

v10.7.20

Description :
-------------
The Python version of the original Mel script

contains algorithms that I use often

Version History :
-----------------
v10.7.20
- added rivet

v10.6.18
- createAxialCorrectionGroup now can correctly zero out joint transformations

v10.5.17
- modifications for Maya 2011 and Pymel 1.0.2

v10.3.24
- added functions to remove pasted from node names

v10.2.16
- fixed concatenateLists function

v10.2.6:
- concatenateLists is updated with a new and much faster method

v10.1.30:
- added: maintainMaximumBackup function
- added: maximum_backup_count option to the backupFile function

v10.1.13
- updated: the validChars splitted in to two different strings now, to have one
  set for file names and another set for valid characters in regular texts

v10.1.5
- added: getBackupNumber, getMaximumBackupNumber

v10.1.4
- embedded_numbers function now converts the input to str, to allow classes
  to be sorted too

v9.12.29
- added getBackUpFiles
- added invalidCharacterRemover

v9.12.24
- added backUpFile

v9.12.23
- added getChildFolders, getChildFiles to easily and quickly get the files and
  folders

v9.11.20
- added unFixWindowsPath to replace \\ with /

v9.11.14
- moved to new versioning scheme
- createAxialCorrectionGroup moved to this script

v1.3.7
- updated padNumber, it now uses zfill method of the python string object
- updated fixWindowsPath, it now uses replace method of the python string
  object

v1.3.6
- added fixWindowsPath which replaces / with \\

v1.3.5
- added stringConditioner to remove any unwanted characters
- added trChars dict as a global variable

v1.3.4
- file_name_conditioner now uses multiple_replace to reduce code replication

v1.3.3
- added multiple_replace

v1.3.2
- fixed a bug in file_name_conditioner, where it was not accepting unicodes

v1.3.1
- fixed a rare bug in padNumber where an error occurs when the number to pad is
  supplied as a string

v1.3.0
- added sort_strings_with_embedded_numbers

v1.2.1
- added __version__
- createFolder now also creates all the intermediate directories

v1.2.0
- added :
  file_name_conditioner
  unique
- moved the non-standar library imports to the functions those needs that
  module

v1.1.0
- added:
  getValidNode
  getAnimCurves
  setAnimCurveColor
  concatenateList

v1.0.0
- initial working version

TODO List :
-----------

--------------------------------------------------------------------------------
"""



import sys, os, re, glob, shutil



__version__= "v10.1.13"



trChars = dict()

## lower letters
#trChars['\xc3\xa7'] = 'c'
#trChars['\xc4\x9f'] = 'g'
#trChars['\xc4\xb1'] = 'i'
#trChars['\xc3\xb6'] = 'o'
#trChars['\xc5\x9f'] = 's'
#trChars['\xc3\xbc'] = 'u'

## upper letters
#trChars['\xc3\x87'] = 'C'
#trChars['\xc4\x9e'] = 'G'
#trChars['\xc4\xb0'] = 'I'
#trChars['\xc3\x96'] = 'O'
#trChars['\xc5\x9e'] = 'S'
#trChars['\xc3\x9c'] = 'U'

# lower letters
trChars[u'\xc3\xa7'] = 'c'
trChars[u'\xc4\x9f'] = 'g'
trChars[u'\xc4\xb1'] = 'i'
trChars[u'\xc3\xb6'] = 'o'
trChars[u'\xc5\x9f'] = 's'
trChars[u'\xc3\xbc'] = 'u'

# upper letters
trChars[u'\xc3\x87'] = 'C'
trChars[u'\xc4\x9e'] = 'G'
trChars[u'\xc4\xb0'] = 'I'
trChars[u'\xc3\x96'] = 'O'
trChars[u'\xc5\x9e'] = 'S'
trChars[u'\xc3\x9c'] = 'U'



validFileNameChars = r' abcdefghijklmnoprqstuvwxyzABCDEFGHIJKLMNOPRQSTUVWXYZ0123456789._-'
validTextChars = validFileNameChars + r'!^+%&(){}[]:;?,|@`\'"/=*~$#'

validFileNameCharsPattern = re.compile(r'[\w\.\-\ ]+')
validTextCharsPattern = re.compile( r'[\w\.\-\ !\^+%&\(\)\{\}\[\]:;?,|\\@`\'"/=*~$#]+' )



#----------------------------------------------------------------------
def getValidDagNode( node ):
    import pymel.core as pm
    
    """returns a valid dag node even the input is string
    """
    try:
        dagNode = pm.nodetypes.DagNode( node )
    except pm.MayaNodeError:
        print 'Error: no node named : %s' % node
        return None
    
    return dagNode



#----------------------------------------------------------------------
def getValidNode( node ):
    import pymel.core as pm
    
    """returns a valid PyNode even the input is string
    """
    try:
        PyNode = pm.PyNode( node )
    except pm.MayaNodeError:
        print 'Error: no node named : %s' % node
        return None
    
    return PyNode



#----------------------------------------------------------------------
def getAnimCurves( node ):
    """returns all the animation curves connected to the
    given node
    """
    
    import pymel.core as pm
    
    # list all connections to the node
    connectedNodes = pm.listConnections( node )
    
    animCurve = "animCurve"
    
    returnList = list()
    
    for cNode in connectedNodes:
        if pm.nodeType( cNode )[0:len(animCurve)] == animCurve:
            returnList.append( cNode )
    
    return returnList



#----------------------------------------------------------------------
def setAnimCurveColor( animCurve, color ):
    """sets animCurve color to color
    """
    import pymel.core as pm
    
    animCurve = getValidNode( animCurve )
    
    animCurve.setAttr( "useCurveColor", True )
    animCurve.setAttr( "curveColor", color, type="double3" )



#----------------------------------------------------------------------
def concatenateLists( *args ):
    """concateLists
    """
    newList = list()
    for arg in args:
        newList.extend( arg )
    
    return newList



#----------------------------------------------------------------------
def file_name_conditioner(fileName):
    """ conditions the file name by replacing the whitespaces and slashes and
    back slahes with underscore ("_") characters
    """
    
    assert(isinstance(fileName, (str, unicode) ) )
    
    fileName = multiple_replace( fileName, trChars )
    
    # make all uppercase
    fileName = fileName.upper()
    
    # replace all the white spaces and slashes
    # with underscore ("_") character
    pattern = re.compile('[\t\n\r\f\v\\\/ ]+')
    fileName = pattern.sub("_",fileName)
    
    return fileName



#----------------------------------------------------------------------
def stringConditioner(
    text,
    allowSpaces=False,
    allowNumbers=True,
    allowNumbersAtBegining=False,
    allowUnderScores=False,
    upperCaseOnly=False,
    capitalize=True
    ):
    """removes any spaces, underscores, and turkish characters from the name
    """
    
    textFixed = unicode( text )
    
    textFixed = multiple_replace( textFixed, trChars )
    
    # remove all the white spaces and slashes
    patternString = '\t\n\r\f\v\\\/,\.;~!"\'^+%&()=?*{}\-'
    
    if not allowSpaces:
        patternString += ' '
    
    if not allowNumbers:
        patternString += '0-9'
    
    if not allowUnderScores:
        patternString += "_"
    
    if allowNumbers and not allowNumbersAtBegining:
        preMatchString = re.compile( '^[0-9]+')
        textFixed = preMatchString.sub("",textFixed)
    
    matchString = '[' + patternString + ']+'
    
    pattern = re.compile(matchString)
    textFixed = pattern.sub("",textFixed)
    
    if capitalize:
        textFixed = textFixed.capitalize()
    
    if upperCaseOnly:
        textFixed = textFixed.upper()
    
    return textFixed



#----------------------------------------------------------------------
def multiple_replace( text, adict ):
    rx = re.compile('|'.join(map(re.escape, adict)))
    def one_xlat(match):
        return adict[match.group(0)]
    return rx.sub(one_xlat, text)



#----------------------------------------------------------------------
def unique(s):
    """ Return a list of elements in s in arbitrary order, but without
    duplicates.
    """
    
    # Try using a set first, because it's the gastest and will usally work
    try:
        return list(set(s))
    except TypeError:
        pass # Move on to the next method
    
    # Since you can't hash all elements, try sorting, to bring equal items
    # together and then weed them out in a single pass
    t = list(s)
    try:
        t.sort()
    except TypeError:
        del t # Move on to the next method
    else:
        # the sort worked, so we are fine
        # do weeding
        return [x for i, x in enumerate(t) if not i or x != t[i-1]]
    # Brute force is all that's left
    u = []
    for x in s:
        if x not in u:
            u.append(x)
    
    return u



#----------------------------------------------------------------------
def padNumber(number, pad):
    """pads a number with zeros
    """
    
    return str(number).zfill(int(pad))



#----------------------------------------------------------------------
def createFolder(folderPath):
    """utility method that creates a folder if it doesn't exists
    """
    
    exists = os.path.exists(folderPath)
    
    if not exists:
        os.makedirs(folderPath)
    
    return exists



#----------------------------------------------------------------------
def embedded_numbers(s):
    re_digits = re.compile(r'(\d+)')
    pieces = re_digits.split(str(s))
    pieces[1::2] = map(int, pieces[1::2])
    return pieces



#----------------------------------------------------------------------
def sort_strings_with_embedded_numbers(alist):
    """sorts a string with embedded numbers
    """
    return sorted(alist, key=embedded_numbers)


#----------------------------------------------------------------------
def fixWindowsPath(path):
    """replaces / with \ for windows
    """
    
    return unicode(path).replace(u'/',u'\\')



#----------------------------------------------------------------------
def unFixWindowsPath():
    """"""
    """replaces \ with / for windows
    """
    
    return unicode(path).replace(u'\\',u'/')



#----------------------------------------------------------------------
def createAxialCorrectionGroup( obj, toParentsOrigin=False ):
    """creates a new parent to zero out the transformations
    
    if toParentsOrigin is set to True, it doesn't zero outs the transformations
    but creates a new parent at the same place of the original parent
    
    returns the acGroup
    """
    import pymel.core as pm
    
    obj = getValidDagNode( obj )
    
    acGroup = pm.group( em=True, n=(obj.name() + "_ACGroup#") )
    
    newName = pm.parent( acGroup, obj )
    acGroup = newName[0]
    
    pm.setAttr( acGroup + ".t", [0, 0, 0])
    pm.setAttr( acGroup + ".r", [0, 0, 0])
    pm.setAttr( acGroup + ".s", [1, 1, 1])
    
    parent = pm.listRelatives( obj, p=True )
    
    if len(parent) != 0:
        pm.parent ( acGroup, parent[0], a=True )
    else:
        pm.parent ( acGroup, w=True )
    
    if toParentsOrigin:
        pm.setAttr( acGroup + ".t", [0, 0, 0])
        pm.setAttr( acGroup + ".r", [0, 0, 0])
        pm.setAttr( acGroup + ".s", [1, 1, 1])
    
    pm.parent ( obj, acGroup, a=True )
    
    # for joints also set the joint orient to zero
    if isinstance(obj, pm.nodetypes.Joint):
        # set the joint rotation and joint orient to zero
        obj.setAttr( 'r', (0, 0, 0) )
        obj.setAttr( 'jo', (0, 0, 0) )
    
    return acGroup



def findFiles(pattern, search_path, pathsep=os.pathsep):
    return glob.glob(os.path.join(path,pattern))



#----------------------------------------------------------------------
def getChildFolders(path, returnFullPath=False):
    """returns the child folders for the given path
    """
    childFolders = os.listdir( path )
    childFolders = filter( os.path.isdir, map( os.path.join, [path] * len(childFolders), childFolders ) )
    
    if returnFullPath:
        return childFolders
    else:
        return map( os.path.basename, childFolders )



#----------------------------------------------------------------------
def getChildFiles(path, returnFullPath=False):
    """returns the child files for the given path
    """
    childFiles = os.listdir( path )
    childFiles = filter( os.path.isfile, map( os.path.join, [path] * len(childFiles), childFiles ) )
    
    if returnFullPath:
        return childFiles
    else:
        return map( os.path.basename, childFiles )



#----------------------------------------------------------------------
def backupFile( fullPath, maximum_backupCount=None ):
    """backups a file by copying it and then renaming it by adding .#.bak
    to the end of the file name
    
    so a file called myText.txt will be backed up as myText.txt.1.bak
    if there is a file with that name than it will increase the bakup number
    """
    
    # check if the file exists
    exists = os.path.exists( fullPath )
    
    if not exists:
        # just return without doing anything
        return
    
    # get the basename of the file
    baseName = os.path.basename( fullPath )
    
    # start the backup number from 1
    backupNo = 1
    backupExtension = '.bak'
    backupFileFullPath = ''
    
    # try to find maximum backup number
    # get the files
    backupNo = getMaximumBackupNumber(fullPath) + 1
    
    # now try to get the maximum backup number
    while True:
        
        backupFileFullPath = fullPath + '.' + str(backupNo) + backupExtension
        
        if os.path.exists( fullPath + '.' + str(backupNo) + backupExtension ):
            backupNo += 1
        else:
            break
    
    # now copy the file with the new name
    shutil.copy( fullPath, backupFileFullPath )
    
    if maximum_backupCount != None:
        maintainMaximumBackupCount( fullPath, maximum_backupCount )
    
    



#----------------------------------------------------------------------
def getBackupFiles( fullPath ):
    """returns the backup files of the given file, returns None if couldn't
    find any
    """
    # for a file lets say .settings.xml the backup file should be names as
    # .settings.xml.1.bak
    # so our search pattern should be
    # .settings.xml.*.bak
    
    backUpExtension = '.bak'
    pattern = fullPath + '.*' + backUpExtension
    
    return sort_strings_with_embedded_numbers( glob.glob(pattern) )



#----------------------------------------------------------------------
def getBackupNumber(fullPath):
    """returns the backup number of the file
    """
    
    backupExtension = '.bak'
    # remove the backupExtension
    # and split the remaining
    # and use the last one as the backupVersion
    
    backupNumber = 0
    
    try:
        backupNumber = int(fullPath[0:-len(backupExtension)].split('.')[-1])
    except (IndexError, ValueError):
        backupNumber = 0
    
    return backupNumber



#----------------------------------------------------------------------
def getMaximumBackupNumber(fullPath):
    """returns the maximum backup number of the file
    """
    
    backupFiles = getBackupFiles( fullPath )
    maximumBackupNumber = 0
    
    if len(backupFiles):
        maximumBackupNumber = getBackupNumber( backupFiles[-1] )
    
    return maximumBackupNumber



#----------------------------------------------------------------------
def maintainMaximumBackupCount( fullPath, maximum_backup_count ):
    """keeps maximum of given number of backups for the given file
    """
    
    if maximum_backup_count == None:
        return
    
    # get the backup files
    backupFiles = getBackupFiles( fullPath )
    
    if len(backupFiles) > maximum_backup_count:
        # delete the older backups
        for backupFile in backupFiles[:-maximum_backup_count]:
            os.remove( backupFile )



#----------------------------------------------------------------------
def invalidCharacterRemover( text, validChars ):
    """its a more stupid way to condition a text
    """
    
    conditionedText = ''
    
    for char in text:
        if char in validChars:
            conditionedText += char
    
    return conditionedText



#----------------------------------------------------------------------
def removeStringFromNodeName( node, removeStr ):
    """removes the given string from the nodes name
    """
    nodeName = node.name()
    
    if nodeName.startswith( removeStr ):
        node.rename( nodeName[len(removeStr):] )



#----------------------------------------------------------------------
def removePastedFromNodeName( node ):
    """removes the 'pasted__' from the given node names
    """
    removeStringFromNodeName( node, 'pasted__' )



#----------------------------------------------------------------------
def removePastedFromNodeNames( nodes ):
    """removes the 'pasted__' from the given nodes names
    """
    for node in nodes:
        removePastedFromNodeName( node )



#----------------------------------------------------------------------
def goHome( node ):
    """sets all the transformations to zero
    """
    
    if node.attr('t').isSettable():
        node.setAttr('t',(0,0,0))
    
    if node.attr('r').isSettable():
        node.setAttr('r',(0,0,0))
    
    if node.attr('s').isSettable():
        node.setAttr('s',(1,1,1))



#----------------------------------------------------------------------
def rivet():
    """the python version of the famous rivet setup from Bazhutkin
    """
    
    import pymel.core as pm
    
    selList = pm.filterExpand(sm=32);
    
    if selList is not None and len(selList) > 0:
        size = len(selList)
        if size != 2:
            raise pm.MayaObjectError('No two edges selected');
        
        edge1 = pm.PyNode(selList[0])
        edge2 = pm.PyNode(selList[1])
        
        edge1Index = edge1.indices()[0]
        edge2Index = edge2.indices()[0]
        
        shape = edge1.node()
        
        cFME1 = pm.createNode('curveFromMeshEdge', n='rivetCurveFromMeshEdge#')
        cFME1.setAttr('ihi',1)
        cFME1.setAttr('ei[0]', edge1Index)
        
        cFME2 = pm.createNode('curveFromMeshEdge', n='rivetCurveFromMeshEdge#')
        cFME2.setAttr('ihi',1)
        cFME2.setAttr('ei[0]', edge2Index)
        
        loft = pm.createNode('loft', n='rivetLoft#')
        loft.setAttr('ic',s=2)
        loft.setAttr('u',1)
        loft.setAttr('rsn',1)
        
        pOSI = pm.createNode('pointOnSurfaceInfo', n='rivetPointOnSurfaceInfo#')
        pOSI.setAttr('turnOnPercentage',1)
        pOSI.setAttr('parameterU', 0.5)
        pOSI.setAttr('parameterV', 0.5)
        
        loft.attr('os') >> pOSI.attr('is')
        cFME1.attr('oc') >> loft.attr('ic[0]')
        cFME2.attr('oc') >> loft.attr('ic[1]')
        shape.attr('w') >> cFME1.attr('im')
        shape.attr('w') >> cFME2.attr('im')
    else:
        selList = pm.filterExpand(sm=41)
        
        
        if selList is not None and len(selList) > 0:
            size = len(selList)
            if size != 1:
                raise pm.MayaObjectError('No one point selected')
            
            point = pm.PyNode(selList[0])
            shape = point.node()
            u = float(point.name().split('][')[0].split('[')[1])
            v = float(point.name().split('][')[1].split(']')[0])
            
            pOSI = pm.createNode('pointOnSurfaceInfo', n='rivetPointOnSurfaceInfo#')
            pOSI.setAttr('turnOnPercentage',0)
            pOSI.setAttr('parameterU', u)
            pOSI.setAttr('parameterV', v)
            shape.attr('ws') >> pOSI.attr('is')
        else:
            raise pm.MayaObjectError('No edges or point selected')
        
    locator = pm.spaceLocator(n='rivet#')
    aimCons = pm.createNode('aimConstraint',
                            p=locator,
                            n=locator.name()+'_rivetAimConstraint#')
    aimCons.setAttr('tg[0].tw', 1)
    aimCons.setAttr('a', (0,1,0))
    aimCons.setAttr('u', (0,0,1))
    aimCons.setAttr('v',k=0)
    aimCons.setAttr('tx',k=0)
    aimCons.setAttr('ty',k=0)
    aimCons.setAttr('tz',k=0)
    aimCons.setAttr('rx',k=0)
    aimCons.setAttr('ry',k=0)
    aimCons.setAttr('rz',k=0)
    aimCons.setAttr('sx',k=0)
    aimCons.setAttr('sy',k=0)
    aimCons.setAttr('sz',k=0)
    
    pOSI.attr('position') >> locator.attr('translate')
    pOSI.attr('n') >> aimCons.attr('tg[0].tt')
    pOSI.attr('tv') >> aimCons.attr('wu')
    aimCons.attr('crx') >> locator.attr('rx')
    aimCons.attr('cry') >> locator.attr('ry')
    aimCons.attr('crz') >> locator.attr('rz')
    
    pm.select( locator )
    return locator

